/* http_defs.cc
   Mathieu Stefani, 01 September 2015

   Implementation of http definitions
*/

#include <iomanip>
#include <iostream>

#include <pistache/common.h>
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif
#include <pistache/thirdparty/date.h>
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#include <pistache/http_defs.h>

namespace Pistache {
namespace Http {

namespace {
using time_point = FullDate::time_point;

bool parse_RFC_1123(const std::string &s, time_point &tp) {
  std::istringstream in{s};
  in >> date::parse("%a, %d %b %Y %T %Z", tp);
  return !in.fail();
}

bool parse_RFC_850(const std::string &s, time_point &tp) {
  std::istringstream in{s};
  in >> date::parse("%a, %d-%b-%y %T %Z", tp);
  return !in.fail();
}

bool parse_asctime(const std::string &s, time_point &tp) {
  std::istringstream in{s};
  in >> date::parse("%a %b %d %T %Y", tp);
  return !in.fail();
}

} // anonymous namespace

CacheDirective::CacheDirective(Directive directive) : directive_(), data() {
  init(directive, std::chrono::seconds(0));
}

CacheDirective::CacheDirective(Directive directive, std::chrono::seconds delta)
    : directive_(), data() {
  init(directive, delta);
}

std::chrono::seconds CacheDirective::delta() const {
  switch (directive_) {
  case MaxAge:
    return std::chrono::seconds(data.maxAge);
  case SMaxAge:
    return std::chrono::seconds(data.sMaxAge);
  case MaxStale:
    return std::chrono::seconds(data.maxStale);
  case MinFresh:
    return std::chrono::seconds(data.minFresh);
  default:
    throw std::domain_error("Invalid operation on cache directive");
  }
}

void CacheDirective::init(Directive directive, std::chrono::seconds delta) {
  directive_ = directive;
  switch (directive) {
  case MaxAge:
    data.maxAge = delta.count();
    break;
  case SMaxAge:
    data.sMaxAge = delta.count();
    break;
  case MaxStale:
    data.maxStale = delta.count();
    break;
  case MinFresh:
    data.minFresh = delta.count();
    break;
  default:
    break;
  }
}

FullDate FullDate::fromString(const std::string &str) {

  FullDate::time_point tp;
  if (parse_RFC_1123(str, tp))
    return FullDate(tp);
  else if (parse_RFC_850(str, tp))
    return FullDate(tp);
  else if (parse_asctime(str, tp))
    return FullDate(tp);

  throw std::runtime_error("Invalid Date format");
}

void FullDate::write(std::ostream &os, Type type) const {
  switch (type) {
  case Type::RFC1123:
    date::to_stream(os, "%a, %d %b %Y %T %Z", date_);
    break;
  case Type::RFC850:
    date::to_stream(os, "%a, %d-%b-%y %T %Z", date_);
    break;
  case Type::AscTime:
    date::to_stream(os, "%a %b %d %T %Y", date_);
    break;
  default:
    std::runtime_error("Invalid use of FullDate::write");
  }
}

const char *versionString(Version version) {
  switch (version) {
  case Version::Http10:
    return "HTTP/1.0";
  case Version::Http11:
    return "HTTP/1.1";
  }

  unreachable();
}

const char *methodString(Method method) {
  switch (method) {
#define METHOD(name, str)                                                      \
  case Method::name:                                                           \
    return str;
    HTTP_METHODS
#undef METHOD
  }

  unreachable();
}

const char *codeString(Code code) {
  switch (code) {
#define CODE(_, name, str)                                                     \
  case Code::name:                                                             \
    return str;
    STATUS_CODES
#undef CODE
  }

  return "";
}

std::ostream &operator<<(std::ostream &os, Version version) {
  os << versionString(version);
  return os;
}

std::ostream &operator<<(std::ostream &os, Method method) {
  os << methodString(method);
  return os;
}

std::ostream &operator<<(std::ostream &os, Code code) {
  os << codeString(code);
  return os;
}

HttpError::HttpError(Code code, std::string reason)
    : code_(static_cast<int>(code)), reason_(std::move(reason)) {}

HttpError::HttpError(int code, std::string reason)
    : code_(code), reason_(std::move(reason)) {}

} // namespace Http
} // namespace Pistache
